// Copyright (c) 2014 Liyong Zou and Int Li
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.



#ifndef TO_LUA_HPP
#define TO_LUA_HPP

extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

#include <cassert>
#include <string>
#include <typeinfo>
#include <memory>
#include <cstring>
#include <sstream>
#include <iostream>

#ifdef USE_BOOST
#include <boost/shared_ptr.hpp>
using boost::shared_ptr;
#else
using std::shared_ptr;
#endif

namespace luaglue {


template<class T>
void to_lua(lua_State *L, T v)
{
    luaL_newmetatable(L, "export_ref_table");
    lua_getfield(L, -1, typeid(T).name());
    void (*export_ref)(lua_State*, const T&) = (void (*)(lua_State*, const T&))lua_topointer(L, -1);
    lua_pop(L, 2);
    export_ref(L, v);
}

template <class TSP>
int __gc(lua_State *L)
{
    const char *metatable = static_cast<const char *>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(metatable);
    shared_ptr<TSP> *psp = (shared_ptr<TSP> *)luaL_checkudata(L, 1, metatable);
    psp->shared_ptr<TSP>::~shared_ptr<TSP>();

    return 0;
}

template <class T>
inline void get_weak_table(lua_State *L)
{
    const std::string &weak_tab_name(std::string(typeid(shared_ptr<T>).name()) + "_weak_table");

    if(luaL_newmetatable (L, weak_tab_name.c_str()))
    {
        luaL_newmetatable (L, weak_tab_name.c_str()); // push xxx_weak_table_meta_table
        lua_pushstring(L, "kv"); // push string "kv"
        lua_setfield(L, -2, "__mode"); // pop string "kv"
        lua_setmetatable(L, -2); // pop xxx_weak_table_meta_table
    }
}

template <class RAWTYPE>
inline void get_raw_meta_table(lua_State *L) 
{
    if(luaL_newmetatable (L, typeid(RAWTYPE).name())) // create and push meta
    {   
        lua_pushvalue(L, -1); // push __index
        lua_setfield(L, -2, "__index"); // pop __index
    }   
}

inline void get_raw_meta_table(lua_State *L, const std::string &rtn)
{
    if(luaL_newmetatable (L, rtn.c_str())) // create and push meta
    {   
        lua_pushvalue(L, -1); // push __index
        lua_setfield(L, -2, "__index"); // pop __index
    }   
}

template <class TSP>
inline void get_meta_table(lua_State *L, shared_ptr<TSP> sp)
{
    std::string sp_id = typeid(shared_ptr<TSP>).name();
    std::string dy_id = typeid(*sp).name();
    std::string metatable = sp_id + "_" + dy_id;

    if(luaL_newmetatable (L, metatable.c_str())) // create and push meta
    {
        lua_pushvalue(L, -1); // push __index
        lua_setfield(L, -2, "__index"); // pop __index

        strcpy(static_cast<char *>(lua_newuserdata(L, metatable.length() + 1)), metatable.c_str());
        lua_pushcclosure(L, __gc<TSP>, 1); // push __gc
        lua_setfield(L, -2, "__gc"); // pop __gc

        get_raw_meta_table(L, dy_id); // push raw metatable
        lua_setmetatable(L, -2); // pop raw metatable
    }
}

template<class T>
inline void to_lua(lua_State *L, shared_ptr<T> sp)
{
    std::ostringstream oss;
    oss << &(*sp);

    get_weak_table<T>(L); // push weak_table
    lua_pushstring(L, oss.str().c_str()); // push key
    lua_rawget(L, -2); // pop key and push usrdata
    if (lua_type(L, -1) == LUA_TNIL)
    {
        lua_pop(L, 1); // pop nil userdata

        new(lua_newuserdata(L, sizeof(sp)))shared_ptr<T>(sp); // push userdata
        get_meta_table<T>(L, sp);
        lua_setmetatable(L, -2); // pop meta table
        lua_pushvalue(L, -1); // dup userdata
        lua_setfield(L, -3, oss.str().c_str()); // pop dup userdata
    }

    lua_remove(L, -2); // remove "xxx_weak_table" from stack
}

template<>
inline void to_lua<int>(lua_State *L, int v)
{
    lua_pushinteger(L, v);
}

///////////Int_li/////////////////////////////
template<>
inline void to_lua<long>(lua_State *L, long v)
{
    lua_pushnumber(L, v);
}

template<>
inline void to_lua<double>(lua_State *L, double v)
{
    lua_pushnumber(L, v);
}

template<>
inline void to_lua<unsigned>(lua_State *L, unsigned v)
{
    lua_pushnumber(L, v);
}

template<>
inline void to_lua<bool>(lua_State *L, bool v)
{
    lua_pushboolean(L, v);
}

template<>
inline void to_lua<short>(lua_State *L, short v)
{
    lua_pushnumber(L, v);
}

template<>
inline void to_lua<float>(lua_State *L, float v)
{
    lua_pushnumber(L, v);
}
//char
template<>
inline void to_lua<char>(lua_State *L, char v)
{
    char str[2] = {v, '\0'};
    lua_pushstring(L, str);
}

///////////end//////////////////////////////

template<>
inline void to_lua<const char *>(lua_State *L, const char *v)
{
    lua_pushstring(L, v);
}

template<>
inline void to_lua<std::string>(lua_State *L, const std::string v)
{
    lua_pushlstring(L, v.c_str(), v.size());
}

}

#endif // TO_LUA_HPP

